package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-ldap/ldap/v3"
	"github.com/joho/godotenv"
	"github.com/robfig/cron/v3"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// MongoDB client
var mongoClient *mongo.Client
var userCollection *mongo.Collection

// AD connection settings
const (
	adServer   = "ldap://10.138.23.11:389"
	baseDN     = "DC=sqmedical,DC=com"
)

// User struct for MongoDB
type User struct {
	Username                     string `bson:"username" json:"username"`
	Email                        string `bson:"email" json:"email"`
	CN                           string `bson:"cn" json:"cn"`
	Company                      string `bson:"company" json:"company"`
	Department                   string `bson:"department" json:"department"`
	Description                  string `bson:"description" json:"description"`
	Manager                      string `bson:"manager" json:"manager"`
	PhysicalDeliveryOfficeName   string `bson:"physicalDeliveryOfficeName" json:"physicalDeliveryOfficeName"`
	PwdLastSet                   string `bson:"pwdLastSet" json:"pwdLastSet"`
	SAMAccountName               string `bson:"sAMAccountName" json:"sAMAccountName"`
	TelephoneNumber              string `bson:"telephoneNumber" json:"telephoneNumber"`
	WhenCreated                  string `bson:"whenCreated" json:"whenCreated"`
	Pager                        string `bson:"pager" json:"pager"`
	FacsimileTelephoneNumber     string `bson:"facsimileTelephoneNumber" json:"facsimileTelephoneNumber"`
	IPAddress                    string `bson:"ipPhone" json:"ipPhone"`
	Info                         string `bson:"info" json:"info"`
	DistinguishedName            string `bson:"distinguishedName" json:"distinguishedName"`
	Title                        string `bson:"title" json:"title"`
	UserAccountControl           string `bson:"userAccountControl" json:"userAccountControl"`

}

// Connect to MongoDB
func connectMongoDB() {
	var err error
	// Create a new client and connect to the database
	mongoURI := os.Getenv("MONGODB_URI")
	mongoClient, err = mongo.Connect(context.TODO(), options.Client().ApplyURI(mongoURI))
	handleError(err, "Failed to create MongoDB client")

	// Ping the database to verify the connection
	err = mongoClient.Ping(context.TODO(), nil)
	handleError(err, "Failed to ping MongoDB")

	userCollection = mongoClient.Database("ad_sync").Collection("users")
	log.Println("Connected to MongoDB")
}

// Handle errors with logging
func handleError(err error, message string) {
	if err != nil {
		log.Fatalf("%s: %v", message, err)
	}
}

// Sync users from Active Directory
func syncUsersFromAD() {
	l, err := ldap.DialURL(adServer)
	handleError(err, "Failed to connect to AD server")
	defer l.Close()

	handleError(l.Bind(os.Getenv("AD_USERNAME"), os.Getenv("AD_PASSWORD")), "Failed to bind to AD server")

	searchRequest := ldap.NewSearchRequest(
		baseDN,
		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
		"(&(objectClass=user)(objectCategory=person))",
		[]string{
			"cn",
			"company",
			"department",
			"description",
			"manager",
			"physicalDeliveryOfficeName",
			"pwdLastSet",
			"sAMAccountName",
			"telephoneNumber",
			"whenCreated",
			"pager",
			"facsimileTelephoneNumber",
			"ipPhone",
			"info",
			"distinguishedName",
			"title",
			"mail",	
			"userAccountControl",
	
	},
		nil,
	)

	searchResult, err := l.Search(searchRequest)
	if err != nil {
		log.Printf("Failed to search in AD: %v", err)
		return
	}

	var users []interface{}
	for _, entry := range searchResult.Entries {
		user := User{
			Username:                     entry.GetAttributeValue("sAMAccountName"),
			Email:                        entry.GetAttributeValue("mail"),
			CN:                           entry.GetAttributeValue("cn"),
			Company:                      entry.GetAttributeValue("company"),
			Department:                   entry.GetAttributeValue("department"),
			Description:                  entry.GetAttributeValue("description"),
			Manager:                      entry.GetAttributeValue("manager"),
			PhysicalDeliveryOfficeName:   entry.GetAttributeValue("physicalDeliveryOfficeName"),
			PwdLastSet:                   entry.GetAttributeValue("pwdLastSet"),
			TelephoneNumber:              entry.GetAttributeValue("telephoneNumber"),
			WhenCreated:                  entry.GetAttributeValue("whenCreated"),
			Pager:                        entry.GetAttributeValue("pager"),
			FacsimileTelephoneNumber:     entry.GetAttributeValue("facsimileTelephoneNumber"),
			IPAddress:                    entry.GetAttributeValue("ipPhone"),
			Info:                         entry.GetAttributeValue("info"),
			DistinguishedName:            entry.GetAttributeValue("distinguishedName"),
			Title:                        entry.GetAttributeValue("title"),
			UserAccountControl:           entry.GetAttributeValue("userAccountControl"),
			SAMAccountName:               entry.GetAttributeValue("sAMAccountName"),
		}
		users = append(users, user)
	}

	insertUsersIntoMongoDB(users)
}

// Insert users into MongoDB
func insertUsersIntoMongoDB(users []interface{}) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	_, err := userCollection.DeleteMany(ctx, bson.M{}) // Clear old data
	handleError(err, "Failed to clear old users")

	_, err = userCollection.InsertMany(ctx, users)
	if err != nil {
		log.Printf("Failed to insert users into MongoDB: %v", err)
	} else {
		log.Printf("Synced %d users from AD to MongoDB", len(users))
	}
}

// Start HTTP server for querying users
func startHTTPServer() {
	router := gin.Default()

	// Endpoint to get all users
	router.GET("/users", func(c *gin.Context) {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()

		cur, err := userCollection.Find(ctx, bson.M{})
		handleError(err, "Failed to fetch users")

		defer cur.Close(ctx)

		var users []User
		if err = cur.All(ctx, &users); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse users"})
			return
		}

		c.JSON(http.StatusOK, users)
	})

	log.Println("Starting HTTP server on :8080")
	router.Run(":8080")
}

func main() {

	// 加载环境变量
	err := godotenv.Load()
	if err != nil {
		log.Fatalf("Error loading .env file")
	}

	// Connect to MongoDB
	connectMongoDB()

	// Start the cron job for syncing users every 30 minutes
	c := cron.New()
	_, err = c.AddFunc("@every 30m", syncUsersFromAD)
	handleError(err, "Failed to schedule sync job")

	c.Start()
	
	// Perform initial sync
	syncUsersFromAD()

	// Start the HTTP server
	startHTTPServer()
}
